import { CacheUtils } from "../cache";
import { ComUtils } from "../Com";
import { HttpClient } from "../http";
import { StrUtils } from "../str";
import { ACCESS_TOKEN_CAHCHE_KEY, AUTH_DIRECT_URL, BACKUP_DIR_NAME, BACKUP_NAME } from "./conf";
import CryptoJS from 'crypto-js'

const client_id = '4887eed333b9484aacd84a573d06f102'
const API_BASE_URL = 'https://openapi.alipan.com'
export class AliyunDriveUtils implements BackupDriver {
    name = 'aliyundrive';
    public CACHE_KEY = 'aliyundrive-'
    private code_challenge = ''
    async init() {
        this.code_challenge = StrUtils.getRandomString(128)
    }
    getAuthUrl() {
        const query = {
            client_id,
            redirect_uri: AUTH_DIRECT_URL,
            response_type: 'code',
            source: 'web',
            code_challenge: this.code_challenge,
            code_challenge_method: 'plain',
            scope: 'user:base,file:all:read,file:all:write'
        }
        return `${API_BASE_URL}/oauth/authorize?${ComUtils.queryToString(query)}`
    }
    async auth() {
        return this.getAuthUrl()
    }
    async cancelAuth() {
        await CacheUtils.remove(this.getCacheKey())
    }
    async getFileHash(blob: Blob) {
        const size = blob.size
        const access_token = await CacheUtils.get(this.getCacheKey())
        const tokenHash = CryptoJS.MD5(access_token).toString().substring(0, 15)
        const tokenCode = StrUtils.stringToUnsignedInts(tokenHash).join('')
        const index = Number(tokenCode) % Number(size)
        const data = {
            Start: index,
            End: index + 8,
        }

        if (data.End >= data.Start) {
            data.End = size
        }

        return btoa(JSON.stringify(data))
    }
    async save(blob: Blob) {
        const accessToken = await CacheUtils.get(this.getCacheKey())
        const headers = {
            Authorization: `Bearer ${accessToken}`,
        }

        const root = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/user/getDriveInfo`, {}, headers).catch(() => undefined)
        if (!root) {
            throw new Error('get drive info error')
        }
        const { backup_drive_id } = root;

        const query = {
            drive_id: backup_drive_id,
            file_path: '/' + BACKUP_DIR_NAME,
        }
        const meta = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/openFile/get_by_path`, query, headers).catch(() => undefined)
        let parent_file_id = 'root'
        if (!meta || !meta.file_id) {
            //create dir
            const dirData = {
                drive_id: backup_drive_id,
                name: BACKUP_DIR_NAME,
                parent_file_id: 'root',
                check_name_mode: 'refuse',
                type: 'folder',
            }
            const dirInfo = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/openFile/create`, dirData, headers);
            if (!dirInfo || !dirInfo.file_id) {
                throw new Error('create dir error')
            }
            parent_file_id = dirInfo.file_id
        } else {
            parent_file_id = meta.file_id
        }

        const contenTemp = CryptoJS.lib.WordArray.create(await blob.arrayBuffer())
        const content_hash = CryptoJS.SHA1(contenTemp).toString()

        const createData = {
            drive_id: backup_drive_id,
            name: BACKUP_NAME,
            parent_file_id,
            check_name_mode: 'refuse',
            size: blob.size,
            type: 'file',
            content_hash,
            content_hash_name: 'sha1',
            part_info_list: [
                {
                    part_number: 1,
                }
            ]
        }
        let uploadInfo = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/openFile/create`, createData, headers);

        if (uploadInfo && uploadInfo.exist) {
            const deleteData = {
                drive_id: backup_drive_id,
                file_id: uploadInfo.file_id,
            }
            await HttpClient.post(`${API_BASE_URL}/adrive/v1.0/openFile/delete`, deleteData, headers)
            uploadInfo = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/openFile/create`, createData, headers);
        }

        console.log('upload', (blob.size / 1024 / 1024).toFixed(2) + 'MB')
        for (const part of uploadInfo.part_info_list) {
            await HttpClient.httpRequest('PUT', part.upload_url, blob, { 'Content-Type': "" }, true)
        }
        const completeData = {
            drive_id: backup_drive_id,
            file_id: uploadInfo.file_id,
            upload_id: uploadInfo.upload_id,
        }
        await HttpClient.post(`${API_BASE_URL}/adrive/v1.0/openFile/complete`, completeData, headers)
    }
    async load() {
        const accessToken = await CacheUtils.get(this.getCacheKey())
        const headers = {
            Authorization: `Bearer ${accessToken}`,
        }

        try {
            const root = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/user/getDriveInfo`, {}, headers).catch(() => undefined)
            if (!root) {
                return
            }
            const { backup_drive_id } = root;

            const query = {
                drive_id: backup_drive_id,
                file_path: '/' + BACKUP_DIR_NAME + '/' + BACKUP_NAME,
            }
            const meta = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/openFile/get_by_path`, query, headers).catch(() => undefined)
            if (!meta || !meta.file_id) {
                return
            }
            const downloadQuery = {
                drive_id: backup_drive_id,
                file_id: meta.file_id,
            }
            const downloadInfo = await HttpClient.post<any>(`${API_BASE_URL}/adrive/v1.0/openFile/getDownloadUrl`, downloadQuery, headers).catch(() => undefined)
            if (!downloadInfo || !downloadInfo.url) {
                return
            }
            console.log(downloadInfo.description)
            const res = await HttpClient.httpRequest<Response>('GET', downloadInfo.url, undefined, { 'Content-Type': "" }, true).catch(() => undefined)
            if (!res) {
                return
            }
            return {
                id: meta.id,
                file: await res.blob()
            }
        } catch (error) {
            console.log(error)
        }

    }
    async isAuth() {
        const accessToken = await CacheUtils.get(this.getCacheKey())
        return !!accessToken
    }
    async delete() {

    }
    public getCacheKey() {
        return this.CACHE_KEY + ACCESS_TOKEN_CAHCHE_KEY
    }
    async getToken(hash: string) {
        const params = new URLSearchParams(hash)
        return params.get('access_token')
    }
    async getCode(hash: string) {
        const params = new URLSearchParams(hash)
        return params.get('code')
    }
    getTokenExpires(hash: string) {
        const params = new URLSearchParams(hash)
        return params.get('expires_in')
    }
    async getTokenWithCode(code: string, _hash: string) {

        const data = {
            code,
            client_id,
            grant_type: 'authorization_code',
            code_verifier: this.code_challenge,
        }
        //reset code_challenge
        this.code_challenge = StrUtils.getRandomString(128)
        const res = await HttpClient.post<any>(`${API_BASE_URL}/oauth/access_token `, data);
        const { access_token, expires_in } = res
        CacheUtils.set(this.getCacheKey(), access_token, Date.now() + 1000 * Number(expires_in || 3600))
        return access_token
    }

}